const simpleGit = require('simple-git');
const { Client } = require('ssh2');
const fs = require('fs').promises;
const fsSync = require('fs');
const path = require('path');
const { exec, execSync } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const webSocketService = require('./websocket.service');
const DeployLog = require('../models/deploy-log.model');
const { rimraf } = require('rimraf');
const fse = require('fs-extra');

class DeployService {
  constructor() {
    this.tempDir = path.join(__dirname, '../../temp');
    this.deployLocks = new Set(); // 存储正在部署的项目ID
    this.init();
  }

  async init() {
    try {
      await fs.mkdir(this.tempDir, { recursive: true });
    } catch (error) {
      console.error('创建临时目录失败:', error);
    }
  }

  async startDeploy(project, deployLog) {
    // 检查是否正在部署
    if (this.deployLocks.has(project._id.toString())) {
      throw new Error('项目正在部署中，请等待当前部署完成');
    }

    const projectDir = path.join(this.tempDir, project._id.toString());
    this.deployLocks.add(project._id.toString());

    try {
      await this.addLog(deployLog, 'info', '开始部署流程');

      // 1. 克隆或拉取代码
      await this.addLog(deployLog, 'info', '正在获取代码...');
      try {
        await this.getCode(project.repositoryUrl, projectDir, project.deploymentConfig.branch);
      } catch (error) {
        await this.addLog(deployLog, 'error', `获取代码失败: ${error.message}`);
        throw error;
      }
      
      // 获取最新commit信息
      try {
        const git = simpleGit(projectDir);
        const latestCommit = await git.log('-1');
        deployLog.commitId = latestCommit.latest.hash;
        await deployLog.save();
      } catch (error) {
        await this.addLog(deployLog, 'warning', `获取commit信息失败: ${error.message}`);
        // 不中断部署流程
      }

      // 2. 安装依赖
      await this.addLog(deployLog, 'info', '正在安装依赖...');
      try {
        await this.execCommand('npm install', projectDir);
      } catch (error) {
        await this.addLog(deployLog, 'error', `安装依赖失败: ${error.message}`);
        throw error;
      }
      
      // 3. 构建项目
      await this.addLog(deployLog, 'info', '正在构建项目...');
      try {
        await this.build(projectDir, project.deploymentConfig.buildCommand);
      } catch (error) {
        await this.addLog(deployLog, 'error', `构建失败: ${error.message}`);
        throw error;
      }
      
      // 4. 部署到服务器
      await this.addLog(deployLog, 'info', '正在部署到服务器...');
      try {
        await this.deployToServer(
          projectDir,
          project.deploymentConfig.outputDir || 'dist',
          project.deploymentConfig.serverConfig
        );
      } catch (error) {
        await this.addLog(deployLog, 'error', `部署到服务器失败: ${error.message}`);
        throw error;
      }
      
      // 更新状态
      deployLog.status = 'success';
      deployLog.endTime = new Date();
      await this.addLog(deployLog, 'info', '部署完成');
      await deployLog.save();

      // 清理临时文件
      try {
        await this.cleanupTempFiles(projectDir);
      } catch (error) {
        console.error('清理临时文件失败:', error);
        // 不中断部署流程
      }
      
      return true;
    } catch (error) {
      console.error('部署失败:', error);

      deployLog.status = 'failed';
      deployLog.endTime = new Date();
      await this.addLog(deployLog, 'error', `部署失败: ${error.message}`);
      await deployLog.save();

      // 即使失败也尝试清理临时文件
      try {
        await this.cleanupTempFiles(projectDir);
      } catch (cleanupError) {
        console.error('清理临时文件失败:', cleanupError);
      }

      throw error;
    } finally {
      // 无论成功失败，都释放锁
      this.deployLocks.delete(project._id.toString());
      // 确保清理临时文件
      await this.cleanupTempFiles(projectDir);
    }
  }

  // 修改清理临时文件的方法
  async cleanupTempFiles(projectDir) {
    if (!fsSync.existsSync(projectDir)) {
      return;
    }

    console.log('开始清理临时文件:', projectDir);
    
    try {
      // 1. 先尝试使用 fs-extra
      await fse.remove(projectDir);
    } catch (error1) {
      console.log('fs-extra 清理失败，尝试系统命令...');
      try {
        // 2. 尝试使用系统命令
        if (process.platform === 'win32') {
          // 先结束可能的 git 进程
          await execPromise('taskkill /F /IM git.exe /T', { ignoreErrors: true });
          await new Promise(resolve => setTimeout(resolve, 1000));
          await execPromise(`rd /s /q "${projectDir}"`);
        } else {
          await execPromise(`rm -rf "${projectDir}"`);
        }
      } catch (error2) {
        console.error('系统命令清理失败:', error2);
        // 3. 最后尝试 rimraf
        try {
          await rimraf(projectDir);
        } catch (error3) {
          console.error('所有清理方法都失败:', error3);
        }
      }
    }

    console.log('临时文件清理完成');
  }

  // 检查路径是否存在
  async pathExists(path) {
    try {
      await fs.access(path);
      return true;
    } catch {
      return false;
    }
  }

  // 递归删除目录
  async removeDir(dir) {
    try {
      const entries = await fs.readdir(dir, { withFileTypes: true });
      
      // 先删除所有文件和子目录
      await Promise.all(entries.map(async entry => {
        const fullPath = path.join(dir, entry.name);
        try {
          if (entry.isDirectory()) {
            await this.removeDir(fullPath);
          } else {
            await fs.unlink(fullPath);
          }
        } catch (error) {
          console.error(`删除 ${fullPath} 失败:`, error);
          // 继续处理其他文件
        }
      }));

      // 最后删除目录本身
      await fs.rmdir(dir);
    } catch (error) {
      console.error(`删除目录失败 ${dir}:`, error);
      throw error;
    }
  }

  // 使用命令行强制删除目录
  async forceRemoveDir(dir) {
    try {
      if (process.platform === 'win32') {
        await execPromise(`rd /s /q "${dir}"`);
      } else {
        await execPromise(`rm -rf "${dir}"`);
      }
      console.log('强制删除目录成功:', dir);
    } catch (error) {
      console.error('强制删除目录失败:', error);
      throw error;
    }
  }

  async getCode(repoUrl, projectDir, branch) {
    try {
      console.log('开始获取代码:', {
        repoUrl,
        projectDir,
        branch
      });

      // 如果目录已存在，使用多种方式尝试清理
      if (fsSync.existsSync(projectDir)) {
        console.log('目录已存在，正在清理...');
        try {
          // 1. 先尝试使用 fs-extra 删除
          await fse.remove(projectDir);
        } catch (error1) {
          console.log('fs-extra 删除失败，尝试使用系统命令...');
          try {
            // 2. 尝试使用系统命令强制删除
            if (process.platform === 'win32') {
              // 先解除文件占用
              await execPromise('taskkill /F /IM git.exe /T', { ignoreErrors: true });
              await new Promise(resolve => setTimeout(resolve, 1000)); // 等待进程完全结束
              await execPromise(`rd /s /q "${projectDir}"`);
            } else {
              await execPromise(`rm -rf "${projectDir}"`);
            }
          } catch (error2) {
            console.error('系统命令删除失败:', error2);
            // 3. 最后尝试使用 rimraf
            try {
              await rimraf(projectDir);
            } catch (error3) {
              console.error('所有清理方法都失败:', error3);
              throw new Error(`清理目录失败: ${error3.message}`);
            }
          }
        }
        console.log('目录清理完成');
      }

      // 确保父目录存在
      const parentDir = path.dirname(projectDir);
      if (!fsSync.existsSync(parentDir)) {
        await fs.mkdir(parentDir, { recursive: true });
      }

      // 等待一小段时间确保文件系统操作完成
      await new Promise(resolve => setTimeout(resolve, 1000));

      // 使用 git clone 的低级选项
      console.log('克隆仓库...');
      const git = simpleGit();
      await git.clone(repoUrl, projectDir, [
        '--depth', '1',  // 浅克隆，只获取最新版本
        '--single-branch',  // 只克隆单个分支
        '--branch', branch,  // 指定分支
        '--no-tags',  // 不下载标签
        '--quiet'  // 减少输出
      ]);
      
      console.log('代码获取完成');
      return true;
    } catch (error) {
      console.error('获取代码失败:', error);
      // 如果失败，再次尝试清理
      try {
        if (fsSync.existsSync(projectDir)) {
          await fse.remove(projectDir).catch(() => {
            // 如果 fs-extra 失败，使用系统命令
            if (process.platform === 'win32') {
              return execPromise(`rd /s /q "${projectDir}"`);
            }
            return execPromise(`rm -rf "${projectDir}"`);
          });
        }
      } catch (cleanupError) {
        console.error('清理失败的克隆目录失败:', cleanupError);
      }
      throw new Error(`获取代码失败: ${error.message}`);
    }
  }

  async build(projectDir, buildCommand) {
    const { stdout, stderr } = await execPromise(buildCommand, {
      cwd: projectDir
    });
    console.log('构建输出:', stdout);
    if (stderr) {
      console.error('构建警告:', stderr);
    }
  }

  async deployToServer(projectDir, outputDir, serverConfig) {
    const maxRetries = 3;
    let retryCount = 0;

    while (retryCount < maxRetries) {
      try {
        return await this._deployToServer(projectDir, outputDir, serverConfig);
      } catch (error) {
        retryCount++;
        if (retryCount === maxRetries) {
          throw error;
        }
        console.log(`部署失败，第 ${retryCount} 次重试...`);
        await new Promise(resolve => setTimeout(resolve, 3000)); // 等待3秒后重试
      }
    }
  }

  async _deployToServer(projectDir, outputDir, serverConfig) {
    return new Promise((resolve, reject) => {
      const conn = new Client();
      
      // 设置连接超时
      const connectionTimeout = setTimeout(() => {
        conn.end();
        reject(new Error('SSH连接超时'));
      }, 30000);

      conn.on('ready', () => {
        clearTimeout(connectionTimeout);
        console.log('SSH连接成功，准备上传文件');
        
        conn.sftp(async (err, sftp) => {
          if (err) {
            console.error('SFTP连接失败:', err);
            conn.end();
            return reject(err);
          }

          try {
            // 确保远程目录存在
            await this.ensureRemoteDir(sftp, serverConfig.path);
            console.log('远程目录已准备:', serverConfig.path);

            // 上传文件
            await this.uploadDir(sftp, path.join(projectDir, outputDir), serverConfig.path);
            conn.end();
            resolve();
          } catch (error) {
            console.error('文件上传失败:', error);
            conn.end();
            reject(error);
          }
        });
      });

      conn.on('error', (err) => {
        clearTimeout(connectionTimeout);
        console.error('SSH连接错误:', err);
        reject(err);
      });

      try {
        conn.connect({
          host: serverConfig.host,
          port: serverConfig.port || 22,
          username: serverConfig.username,
          password: serverConfig.password,
          tryKeyboard: true,
          readyTimeout: 30000,
          keepaliveInterval: 10000
        });
      } catch (error) {
        clearTimeout(connectionTimeout);
        console.error('SSH连接配置错误:', error);
        reject(error);
      }
    });
  }

  async uploadDir(sftp, localPath, remotePath) {
    console.log(`正在上传目录: ${localPath} -> ${remotePath}`);
    const files = await fs.readdir(localPath);
    
    for (const file of files) {
      const localFilePath = path.join(localPath, file);
      const remoteFilePath = path.posix.join(remotePath, file).replace(/\\/g, '/');
      
      const stats = await fs.stat(localFilePath);
      if (stats.isDirectory()) {
        // 确保远程子目录存在
        await this.ensureRemoteDir(sftp, remoteFilePath);
        await this.uploadDir(sftp, localFilePath, remoteFilePath);
      } else {
        try {
          await this.uploadFile(sftp, localFilePath, remoteFilePath);
          console.log(`文件上传成功: ${remoteFilePath}`);
        } catch (error) {
          console.error(`文件上传失败 ${localFilePath} -> ${remoteFilePath}:`, error);
          throw error;
        }
      }
    }
  }

  async ensureRemoteDir(sftp, remotePath) {
    const dirs = remotePath.split('/').filter(Boolean);
    let currentPath = '/';

    for (const dir of dirs) {
      currentPath = path.posix.join(currentPath, dir);
      try {
        await this.sftpMkdir(sftp, currentPath);
      } catch (error) {
        // 如果目录已存在（错误码为4），则继续
        if (error.code !== 4) {
          throw error;
        }
      }
    }
  }

  async sftpMkdir(sftp, path) {
    return new Promise((resolve, reject) => {
      sftp.mkdir(path, (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  async uploadFile(sftp, localPath, remotePath) {
    return new Promise((resolve, reject) => {
      sftp.fastPut(localPath, remotePath, {
        // 添加上传选项
        mode: 0o644, // 文件权限
        concurrency: 1, // 并发数
        chunkSize: 32768 // 分块大小
      }, (err) => {
        if (err) {
          console.error(`上传文件失败 ${localPath} -> ${remotePath}:`, err);
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  async addLog(deployLog, level, message) {
    if (!deployLog.logs) {
      deployLog.logs = [];
    }
    
    const logEntry = {
      level,
      message,
      timestamp: new Date()
    };
    
    deployLog.logs.push(logEntry);
    
    try {
      await deployLog.save();
      // 添加错误处理
      try {
        webSocketService.sendDeployLog(deployLog.projectId.toString(), logEntry);
      } catch (wsError) {
        console.error('WebSocket发送日志失败:', wsError);
        // 尝试重新连接
        webSocketService.reconnect();
      }
    } catch (error) {
      console.error('保存部署日志失败:', error);
    }
  }

  async execCommand(command, cwd) {
    return new Promise((resolve, reject) => {
      const timeout = 300000; // 5分钟超时
      const child = exec(command, { 
        cwd,
        maxBuffer: 1024 * 1024 * 10 // 增加缓冲区大小到10MB
      });

      let output = '';
      let errorOutput = '';

      child.stdout.on('data', (data) => {
        output += data;
        console.log(`命令输出: ${data}`);
      });

      child.stderr.on('data', (data) => {
        errorOutput += data;
        console.warn(`命令警告: ${data}`);
      });

      child.on('error', (error) => {
        console.error(`命令执行错误: ${error.message}`);
        reject(error);
      });

      child.on('exit', (code) => {
        if (code === 0) {
          resolve(output);
        } else {
          reject(new Error(`命令执行失败，退出码: ${code}\n${errorOutput}`));
        }
      });

      // 添加超时处理
      const timer = setTimeout(() => {
        child.kill();
        reject(new Error(`命令执行超时 (${timeout/1000}秒): ${command}`));
      }, timeout);

      // 清理定时器
      child.on('exit', () => clearTimeout(timer));
    });
  }

  // 更新部署状态时发送通知
  async updateDeployStatus(project, status) {
    project.status = status;
    await project.save();
    try {
      webSocketService.sendDeployStatus(project._id.toString(), status);
    } catch (error) {
      console.error('WebSocket发送状态更新失败:', error);
      webSocketService.reconnect();
    }
  }

  async rollback(project, targetDeployLog) {
    const projectDir = path.join(this.tempDir, project._id.toString());
    let rollbackLog = null;
    
    try {
      // 创建新的部署日志
      rollbackLog = new DeployLog({
        projectId: project._id,
        branch: targetDeployLog.branch,
        commitId: targetDeployLog.commitId,
        deployedBy: targetDeployLog.deployedBy,
        logs: []
      });
      await rollbackLog.save();

      await this.addLog(rollbackLog, 'info', `开始回滚到版本 ${targetDeployLog.commitId.slice(0, 7)}`);

      // 1. 克隆代码
      await this.addLog(rollbackLog, 'info', '正在获取代码...');
      await this.getCode(project.repositoryUrl, projectDir, targetDeployLog.branch);
      
      // 2. 切换到指定的commit
      const git = simpleGit(projectDir);
      await git.checkout(targetDeployLog.commitId);
      await this.addLog(rollbackLog, 'info', `已切换到commit: ${targetDeployLog.commitId.slice(0, 7)}`);

      // 3. 安装依赖和构建
      await this.addLog(rollbackLog, 'info', '正在安装依赖...');
      await this.execCommand('npm install', projectDir);
      
      await this.addLog(rollbackLog, 'info', '正在构建项目...');
      await this.build(projectDir, project.deploymentConfig.buildCommand);
      
      // 4. 部署到服务器
      await this.addLog(rollbackLog, 'info', '正在部署到服务器...');
      await this.deployToServer(
        projectDir,
        project.deploymentConfig.outputDir || 'dist',
        project.deploymentConfig.serverConfig
      );
      
      // 更新状态
      project.status = 'success';
      project.lastDeployTime = new Date();
      await project.save();

      rollbackLog.status = 'success';
      rollbackLog.endTime = new Date();
      await this.addLog(rollbackLog, 'info', '回滚完成');
      await rollbackLog.save();

      // 清理临时文件
      await this.cleanupTempFiles(projectDir);
      
      return true;
    } catch (error) {
      console.error('回滚失败:', error);

      // 更新状态
      project.status = 'failed';
      await project.save();

      if (rollbackLog) {
        rollbackLog.status = 'failed';
        rollbackLog.endTime = new Date();
        await this.addLog(rollbackLog, 'error', `回滚失败: ${error.message}`);
        await rollbackLog.save();
      }

      // 即使失败也清理临时文件
      await this.cleanupTempFiles(projectDir);

      throw error;
    }
  }
}

module.exports = new DeployService(); 